// 这里只写核心代码 错误判断就不写
// 组合式设计模式 = 构造函数 + 原型对象
function Vue (options) {
  // console.log(options, typeof options)
  if (typeof options !== 'object') throw TypeError('参数类型错误')
  // 在构造函数中保存传递过来的参数对象
  this.$options = options
  // 将vue的监管区域保存起来
  this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
  // 将data保存起来便于后续进行数据劫持
  this._data = options.data
  // 这里普及基本常识 约定
  // _开始的属性表示私有属性 不允许外界去访问的 Symbol制作私有属性更好点
  this.initData()
}
Vue.prototype = {
  initData () {
    // 完成 将new Vue传入的对象中的属性放到this上 （不是简单将对象中的属性拷贝到this上）
    // 我是将对象中的属性都通过数据劫持代理到this上
    // 这里老师使用for in演示
    var data = this._data //  { name: '张三', age: 18,  yy: ...}
    for (const key in data) { // 这里必须使用const或let
      // console.log(key) // name age yy
      // 如果this没有这个属性就会添加这个属性然后对这个属性就行数据劫持
      // 思考 如果属性比如yy的值是一个复杂数据类型 难道我们还要数据递归劫持么？？？
      // 没必要 我们只需要出发this.属性的获取和设置操作的时候 去触发this._data中的属性设置和获取即可
      Object.defineProperty(this, key, {
        set: function reactiveSetter (newVal) {
          this._data[key] = newVal
        },
        get: function reactiveGetter () {
          return this._data[key]
        }
      })
    }

    
    // vue初始化数据的方法
    observe(this._data)
  }
}
// 定义一个方法这个方法来实现观察者
function observe (data) {
  // 这里判断一下如果是一个对象我才劫持
  if (typeof data !== 'object') return
  // 调用Observe 完成数据劫持
  new Observer(data)
}
// 定义一个响应式数据的方法 为了复用
function defineReactive (data, property) {
  let value = data[property]
  // 如果value是复杂数据类型 这个时候我们需要递归数据劫持
  observe(value) // 就这么一句就实现了递归数据劫持
  // 要为每个属性都添加一个Dep收集
  let dep = new Dep()
  console.log(property, dep)
  Object.defineProperty(data, property, {
    // 可以配置 可写入 可枚举
    // enumerable: true,
    set: function reactiveSetter (newVal) {
      // 当要劫持的对象上的属性被修改了 会触发
      value = newVal
      // console.log(newVal, `属性${property}被修改了`)
      // 通知所有订阅者更新
      dep.notify()
    },
    get: function reactiveGetter () {
      // // 当要劫持的对象上的属性被获取了 会触发
      // console.log(`属性${property}被获取了`)
      // 获取说明你订阅了我 属性改变应该通知页面更新
      dep.depend()
      return value
    }
  })
}
// 这里用ES6语法 Observer他是最终实现数据劫持的类
class Observer {
  constructor (data) { // 这里相当于构造函数
    this.data = data // this.data就是要劫持的数据对象
    this.walk(data) // 将要劫持的data作为实参传递给walk
  }
  // 下面相对于原型上的方法
  walk (data) {
    Object.keys(data).forEach(property => {
      // console.log(item) // name age
      // 下面我们就要对属性进行数据劫持
      // Object.defineProperty(要劫持的数据对象, 属性, {})
      defineReactive(data, property)
    })
  }
}

// 定义一个属性依赖收集器
class Dep {
  constructor () {
    this.subs = []
  }
  notify () {
    // 通过所有的订阅者更新
    console.log('通知了所有的订阅者更新了')
  }
  depend () {
    // 添加订阅者
    // this.subs.push()
    console.log('添加订阅者')
  }
}
